package com.github.tbouron.shakedetector.library;

import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.sensor.agent.CategoryOrientationAgent;
import ohos.sensor.bean.CategoryOrientation;
import ohos.sensor.data.CategoryOrientationData;
import ohos.sensor.listener.ICategoryOrientationDataCallback;

import java.util.ArrayList;

/**
 * 加速度检测类
 *
 * @since 2021-03-20
 */
public class ShakeDetector implements ICategoryOrientationDataCallback {
    /**
     * 默认强度数
     */
    public static final float DEFAULT_THRESHOLD_ACCELERATION = 1200f;
    /**
     * 默认达标次数
     */
    public static final int DEFAULT_THRESHOLD_SHAKE_NUMBER = 3;
    /**
     * 实际震动强度
     */
    public static float mThresholdAcceleration = 1200f ;
    /**
     * 2
     */
    public static final int NUM2 = 2;
    /**
     * 1000
     */
    public static final int NUM10000 = 10000;
    /**
     * 默认震动触发次数
     */
    public static int mThresholdShakeNumber = 3;
    private static CategoryOrientation orientationSensor;
    private static CategoryOrientationAgent categoryOrientationAgent = new CategoryOrientationAgent();
    private static final long INTERVAL = 500L;
    private static ShakeDetector mSensorEventListener;

    private OnShakeListener mShakeListener;
    private ArrayList<SensorBundle> mSensorBundles;
    private Object mLock;
    private int direction = 1;
    private long interval = 500L;
    /**
     * 上一次检测的时间
     */
    private long mLastUpdateTime = 0L;
    /**
     * 上一次检测时，加速度在x、y、z方向上的分量，用于和当前加速度比较求差。
     */
    private float mLastX;
    private float mLastY;
    private float mLastZ;
    /**
     * 消息
     */
    public static final int CODE_DOWNLOAD_FILE1 = 1;
    /**
     * handler
     */
    private EventHandler myEvenHandler = new EventHandler(EventRunner.getMainEventRunner()){
        @Override
        protected void processEvent(InnerEvent event) {
            super.processEvent(event);
            if (event == null) {
                return;
            }
            int eventId = event.eventId;
            if(eventId == CODE_DOWNLOAD_FILE1){
                mShakeListener.OnShake();
            }
        }
    };

    /**
     * 摇动设备后要调用的回调的接口定义
     *
     * @since 2021-03-20
     */
    public interface OnShakeListener {
        /**
         * Called when the device has been shaken.
         */
        void OnShake();
    }

    private ShakeDetector(OnShakeListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("Shake listener must not be null");
        }
        mShakeListener = listener;
        mSensorBundles = new ArrayList<SensorBundle>();
        mLock = new Object();
    }

    /**
     * 创建一个震动检测器，并开始侦听设备震动。
     *
     * @param context 上下文
     * @param listener 监听晃动
     */
    public static void create(Context context, OnShakeListener listener) {
        mSensorEventListener = new ShakeDetector(listener);
        orientationSensor = categoryOrientationAgent.getSingleSensor(CategoryOrientation.SENSOR_TYPE_ORIENTATION);
    }

    /**
     * 启动先前创建的震动检测器。如果以前没有创建检测器，
     * 则该方法将不会创建一个检测器，并且将返回false。
     *
     * @return 如果震动检测器已正确启动，则返回true，否则返回false
     */
    public static boolean start() {
        if (orientationSensor != null && categoryOrientationAgent != null && mSensorEventListener != null) {
            return categoryOrientationAgent.setSensorDataCallback(mSensorEventListener, orientationSensor, INTERVAL);
        }
        return false;
    }

    /**
     * 停止先前创建的震动检测器。如果以前没有创建检测器，则该方法将执行任何操作。
     */
    public static void stop() {
        if (orientationSensor != null) {
            categoryOrientationAgent.releaseSensorDataCallback(
                    mSensorEventListener, orientationSensor);
        }
    }

    /**
     * Releases all resources previously created.
     */
    public static void destroy() {
        orientationSensor = null;
        mSensorEventListener = null;
    }

    /**
     * 您可以根据使用情况更新震动检测器的配置，但默认设置在大多数情况下都适用。
     * 用于灵敏度，
     * 将用于所需的抖动次数。
     *
     * @param shakeNumber 触发一次摇动所需的摇动次数（大约）
     * @param sensibility 灵敏度（以G表示）是需要考虑的最小加速度。号码越多，摇动设备来触发摇动的难度就越大。
     */
    public static void updateConfiguration(float sensibility, int shakeNumber) {
        mSensorEventListener.setConfiguration(sensibility, shakeNumber);
    }

    /**
     * 设置强度和次数触发条件
     *
     * @param sensibility 强度
     * @param shakeNumber 次数
     */
    private void setConfiguration(float sensibility, int shakeNumber) {
        mThresholdAcceleration = sensibility;
        mThresholdShakeNumber = shakeNumber;
        synchronized (mLock) {
            mSensorBundles.clear();
        }
    }

    /**
     * 对接收的categoryOrientationData传感器数据对象解析和使用
     *
     * @param categoryOrientationData CategoryOrientationData
     */
    @Override
    public void onSensorDataModified(CategoryOrientationData categoryOrientationData) {
        SensorBundle sensorBundle =
                new SensorBundle(categoryOrientationData.values[0],
                        categoryOrientationData.values[1],
                        categoryOrientationData.values[NUM2],
                        categoryOrientationData.timestamp);
        synchronized (mLock) {
            if (mSensorBundles.size() == 0) {
                mSensorBundles.add(sensorBundle);
            } else if (sensorBundle.getTimestamp() - mSensorBundles.get(mSensorBundles.size() - 1)
                    .getTimestamp() > interval) {
                mSensorBundles.add(sensorBundle);
            }
        }
        performCheck2(categoryOrientationData);
    }

    private void performCheck2(CategoryOrientationData categoryOrientationData) {
        synchronized (mLock) {
            long currentTime = System.currentTimeMillis();
            long diffTime = currentTime - mLastUpdateTime;
            if (diffTime < interval) {
                return;
            }
            mLastUpdateTime = currentTime;
            float xx = categoryOrientationData.getValues()[0];
            float yy = categoryOrientationData.getValues()[1];
            float zz = categoryOrientationData.getValues()[NUM2];
            float deltaX = xx - mLastX;
            float deltaY = yy - mLastY;
            float deltaZ = zz - mLastZ;
            mLastX = xx;
            mLastY = yy;
            mLastZ = zz;
            float delta = (float) (Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ)
                    / diffTime * NUM10000);
            /**
             * 当加速度的差值大于指定的阈值，认为这是一个摇晃
             */
            if (delta > mThresholdAcceleration) {
                direction += 1;
            }
            if (direction >= mThresholdShakeNumber) {
                direction = 0;
                myEvenHandler.sendEvent(CODE_DOWNLOAD_FILE1);
                mSensorBundles.clear();
            }
        }
    }

    @Override
    public void onAccuracyDataModified(CategoryOrientation categoryOrientation, int i) {
    }

    // 传感器执行命令回调
    @Override
    public void onCommandCompleted(CategoryOrientation categoryOrientation) {
    }

    /**
     * 方便的对象，用于存储设备的3轴加速度以及当前捕获的时间
     *
     * @since 2021-03-20
     */
    public static class SensorBundle {
        /**
         * X轴上的加速度
         */
        private final float mXAcc;
        /**
         * Y轴上的加速度。
         */
        private final float mYAcc;
        /**
         * Z轴上的加速度。
         */
        private final float mZAcc;
        /**
         * 记录何时记录的时间戳。
         */
        private final long mTimestamp;

        /**
         * 构造
         *
         * @param xacc x
         * @param yacc y
         * @param zacc z
         * @param timestamp 时间戳
         */
        public SensorBundle(float xacc, float yacc, float zacc, long timestamp) {
            mXAcc = xacc;
            mYAcc = yacc;
            mZAcc = zacc;
            mTimestamp = timestamp;
        }

        /**
         * x
         *
         * @return x
         */
        public float getxacc() {
            return mXAcc;
        }

        /**
         * y
         *
         * @return y
         */
        public float getyacc() {
            return mYAcc;
        }

        /**
         * z
         *
         * @return z
         */
        public float getzacc() {
            return mZAcc;
        }

        /**
         * Timestamp
         *
         * @return Timestamp
         */
        public long getTimestamp() {
            return mTimestamp;
        }

        @Override
        public String toString() {
            return "SensorBundle{"
                    + "mXAcc=" + mXAcc
                    + ", mYAcc=" + mYAcc
                    + ", mZAcc=" + mZAcc
                    + ", mTimestamp="
                    + mTimestamp
                    + '}';
        }
    }
}
